package graphics.datastructures;

import static util.MatrixOperationsF.add;
import static util.MatrixOperationsF.ar;
import static util.MatrixOperationsF.cloo;
import static util.MatrixOperationsF.cross_product;
import static util.MatrixOperationsF.dist;
import static util.MatrixOperationsF.multiply;
import static util.MatrixOperationsF.negate;
import static util.MatrixOperationsF.normalize;
import static util.MatrixOperationsF.print;
import graphics.raytracing.CastResult;
import graphics.raytracing.Ray;
import graphics.raytracing.Tracable;

import java.io.Serializable;

public class Triangle implements Tracable<Triangle>,Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -7839200134058166479L;
	float[][] _coords;
	float[][] _normals;
	private float[] _facenormal;
	private final double e=0.0001f;
	private BoundingBox<Triangle> _box;
	/**
	 * Triangle constructor
	 * @param triangle
	 * The points of the triangle
	 * @param normals
	 * The normals on each of the point
	 */
	public Triangle(float[][] triangle, float[][] normals) {
		_coords = cloo(triangle);
		_normals = cloo(normals);
		_box=new BoundingBox<Triangle>(getBB()[0], getBB()[1]);
		_facenormal = normalize(cross_product(add(triangle[0],negate(triangle[1])),add(triangle[0],negate(triangle[2]))));
	}

	

	public boolean intersects(Ray ray) {
		return cast(ray)!=null;
	}

	@Override
	public CastResult<Triangle> cast(Ray ray) {
	if(!hitBoundingbox(ray))
		return null;
	if(hitFromBehind(ray))
		return null;
	double[] hit = determine_hit_location(ray);
	double b = hit[0];//determine_hit_location(ray)[0];
	double g = hit[1];//etermine_hit_location(ray)[1]
	if(b+g>1+e)
		return null;
	if(b<-e||b>1+e)
		return null;
	if(g<-e||g>1+e)
		return null;
	return new CastResult<Triangle>(this, add(ray.getOrigin(),multiply(ray.getDirection(),(float)hit[2])),getNormal((float)b,(float)g),dist(add(ray.getOrigin(),negate(fd(hit)))));
	}
	private boolean hitBoundingbox(Ray ray) {
		return _box.intersects(ray);
	}

	private static float[] fd(double[] rv){
		float[] rve= new float[rv.length];
		for (int i = 0; i < rv.length; i++) {
			rve[i]=(float)rv[i];
		}
		return rve;
	}

	private boolean hitFromBehind(Ray ray) {
		return multiply(normalize(ray.getDirection()),getTrueNormal())>0;
	}


	/**
	 * Interpolated normal
	 * @param b
	 * @param g
	 * @return
	 */
	private float[] getNormal(float b, float g) {
		float[] result = new float[4];
		float a = 1 - b - g;
		result[0] = a * _normals[0][0] + b * _normals[1][0] + g * _normals[2][0];
		result[1] = a * _normals[0][1] + b * _normals[1][1] + g * _normals[2][1];
		result[2] = a * _normals[0][2] + b * _normals[1][2] + g * _normals[2][2];
		result[3] = 1;
		result[3]=1;
		return result;
	}
	/**
	 * Face normal vector, normalized.
	 * @return
	 */
	private float[] getTrueNormal()
	{
		return _facenormal;
	}


	/**
	 * 
	 * @param ray
	 * @return {Beta,Gamma,t}
	 */
	private double[] fromd(float... f)
	{
		double[] rv = new double[f.length];
		for (int i = 0; i < rv.length; i++) {
			rv[i]=f[i];
		}
		return rv;
	}
	private double[] fromd(double...d)
	{
		return d;
	}
	public double[] determine_hit_location(Ray ray) {
		final int x =0;
		final int y =1;
		final int z =2;
		double[] a = fromd(_coords[0]);
		double[] b = fromd(_coords[1]);
		double[] c = fromd(_coords[2]);
		double[] eye = fromd(ray.getOrigin());
		double[] direction =fromd( ray.getDirection());
		//magic, it works, dont figure it out
		double _a = a[x]-b[x];
		double _b = a[y]-b[y];
		double _c = a[z]-b[z];
		double _d = a[x]-c[x];
		double _e = a[y]-c[y];
		double _f = a[z]-c[z];
		double _g=direction[x];
		double _h=direction[y];
		double _i=direction[z];
		double _j=a[x]-eye[x];
		double _k=a[y]-eye[y];
		double _l=a[z]-eye[z];
		double M = _a*(_e*_i-_h*_f)+_b*(_g*_f-_d*_i)+_c*(_d*_h-_e*_g);
 		double beta=( _j*(_e*_i-_h*_f)+_k*(_g*_f-_d*_i)+_l*(_d*_h-_e*_g))/M;
 		double gamm =( _i*(_a*_k-_j*_b)+_h*(_j*_c-_a*_l)+_g*(_b*_l-_k*_c))/M;
 		double t = -(_f*(_a*_k-_j*_b)+_e*(_j*_c-_a*_l)+_d*(_b*_l-_k*_c))/M;
		return fromd(beta,gamm,t);
	}
	@Override
	public String toString()
	{
		return print(_coords[0])+print(_coords[1])+print(_coords[2]);
	}



	@Override
	public float[][] getBB() {
		float[][] rv = new float[2][];
		rv[0]=ar(Float.MAX_VALUE,Float.MAX_VALUE,Float.MAX_VALUE);
		rv[1]=negate(rv[0]);
		for (int i = 0; i < _coords.length; i++) {
			for (int xyz = 0; xyz < 3; xyz++) {
				rv[0][xyz]=Math.min(rv[0][xyz],_coords[i][xyz]);
				rv[1][xyz]=Math.max(rv[1][xyz],_coords[i][xyz]);
			}
					
		}
		return rv;
	}



	public float[][] tri() {
		return _coords;
	}



	@Override
	public BoundingBox<Triangle> getBoundingBox() {
		return this._box;
	}

}
